package com.aphrodite.ui.widgets.barcode;

import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

import com.aphrodite.ui.widgets.Widget;
import com.aphrodite.util.geom.Dimension;
import com.aphrodite.util.geom.Rectangle;

/**
 * Generic barcode representation
 * 
 * @author kentelt
 * 
 */
public abstract class Barcode extends Widget {
	private static int barCodeBG = 0xffffff;

	private static int barCodeFG = 0;

	private static final int DEFAULT_BAR_HEIGHT = 50;
	String data;
	boolean drawingQuietSection = true;
	boolean drawingText;
	// protected int barWidth = 2;
	// protected int barHeight;
	private Font font;
	String label;

	private Dimension size;

	Barcode(String data, int height) throws BarcodeException {
		super(null, 0, height);
		if (data == null || data.length() == 0) {
			throw new BarcodeException("Data to encode cannot be empty");
		}
		this.data = data;
		int minHeight = height; // calculateMinimumBarHeight(getResolution());
		if (minHeight > 0) {
			super.height = minHeight;
		} else {
			super.height = Barcode.DEFAULT_BAR_HEIGHT;
		}
		this.drawingText = true;
		// super.fgColor = 0;
		// super.bgColor = 0xffffff;
		super.width = 2;
		// invalidateSize();
	}

	abstract Module calculateChecksum();

	private Dimension calculateSize() {
		return new Dimension(super.width, super.height);
		// Dimension d = new Dimension();
		// if (EnvironmentFactory.getEnvironment() instanceof
		// HeadlessEnvironment) {
		// try {
		// d = draw(new SizingOutput(font, fgColor, bgColor), 0, 0, barWidth,
		// barHeight);
		// } catch (OutputException e) {
		// }
		// } else {
		// try {
		// FontMetrics fontMetrics = null;
		// if (font != null) {
		// fontMetrics = getFontMetrics(font);
		// }
		// d = draw(new SizingOutput(font, fontMetrics, getForeground(),
		// getBackground()), 0, 0, barWidth,
		// barHeight);
		// } catch (OutputException e) {
		// }
		// }
		//
		// return d;
	}

	/**
	 * Renders this <code>Barcode</code> at the specified location in the
	 * specified {@link java.awt.Graphics2D Graphics2D} context. The origin of
	 * the layout is placed at x,&nbsp;y. Rendering may touch any point within
	 * <code>getBounds()</code> of this position. This leaves the
	 * <code>g2</code> unchanged.
	 * 
	 * @param g
	 *            The graphics context
	 * @param x
	 *            The horizontal value of the upper left co-ordinate of the
	 *            bounding box
	 * @param y
	 *            The vertical value of the upper left co-ordinate of the
	 *            bounding box
	 */
	void draw(Graphics g, int x, int y) throws OutputException {
		this.x = x;
		this.y = y;
		Output output = new GraphicsOutput(g, font, barCodeFG, barCodeBG);
		size = draw(output, x, y, super.width, super.height);
	}

	abstract Dimension draw(Output output, int x, int y, int barWidth, int barHeight) throws OutputException;

	int drawModule(Module module, Output output, int x, int y, int barWidth, int barHeight) throws OutputException {
		if (module == null) {
			return 0;
		}
		return module.draw(output, x, y, barWidth, barHeight);
	}

	abstract Module[] encodeData();

	private Dimension getActualSize() {
		if (size == null) {
			size = calculateSize();
		}
		return size;
	}

	public Rectangle getBounds() {
		return new Rectangle(getX(), getY(), (int) getActualSize().getWidth() + getX(), (int) getActualSize()
				.getHeight()
				+ getY());

	}

	/**
	 * Returns the data that the barcode is coding for.
	 * 
	 * @return The barcode raw data
	 */
	public String getData() {
		return data;
	}

	// /**
	// * Sets the desired output resolution for the barcode. This method should
	// be
	// * used in cases where the barcode is either being outputted to a device
	// * other than the screen, or the barcode is being generated on a headless
	// * machine (e.g. a rack mounted server) and the screen resolution cannot
	// be
	// * determined. Note that is the barcode is generated in either of these
	// * situations and this method has not been called, the resolution is
	// assumed
	// * to be 72 dots per inch.
	// *
	// * @param resolution
	// * The desired output resolution (in dots per inch)
	// */
	// public void setResolution(int resolution) {
	// if (resolution > 0) {
	// this.resolution = resolution;
	// int newHeight = calculateMinimumBarHeight(getResolution());
	// if (newHeight > this.barHeight) {
	// this.barHeight = newHeight;
	// }
	// invalidateSize();
	// }
	// }

	/**
	 * 
	 * 
	 * @return The height of this component
	 */
	public int getHeight() {
		return (int) getActualSize().getHeight();
	}

	abstract Module getPostAmble();

	abstract Module getPreAmble();

	/**
	 * 
	 * 
	 * @return The width of this component
	 */
	public int getWidth() {
		return (int) getActualSize().getWidth();
	}

	private void invalidateSize() {
		size = null;
	}

	/**
	 * Indicates whether the barcode is drawing leading and trailing white
	 * space/quiet area.
	 * 
	 * @return True if the quiet area/white space is drawn, false otherwise
	 */
	public boolean isDrawingQuietSection() {
		return drawingQuietSection;
	}

	/**
	 * Indicates whether the barcode is drawing a text label underneath the
	 * barcode or not.
	 * 
	 * @return True if the text is drawn, false otherwise
	 */
	public boolean isDrawingText() {
		return drawingText;
	}

	public void output(Output output) throws OutputException {
		draw(output, 0, 0, this.width, this.height);
	}

	public void render(Graphics g) {
		if (isVisible) {
			try {
				System.out.println("Barcode x:" + x + " y: " + y);
				// draw(g, x, y);
				// g.translate(10, 50);
				// draw(g, 10, 50);
				draw(g, 0, 0);
				// g.translate(-10, -50);
			} catch (OutputException e) {
				// Don't draw anything
			}
		}
	}

	/**
	 * Sets the desired height for the bars in the barcode (in pixels). Note
	 * that some barcode implementations will not allow the height to go below a
	 * minimum size. This is not the height of the component as a whole, as it
	 * does not specify the height of any text that may be drawn and does not
	 * include borders. <p/> Note that changing this setting after a barcode has
	 * been drawn will invalidate the component and may force a refresh.
	 * 
	 * @param barHeight
	 *            The desired height of the barcode bars in pixels
	 */
	public void setBarHeight(int barHeight) {
		// There is a minimum bar height that we must enforce
		// if (barHeight > calculateMinimumBarHeight(getResolution())) {
		this.height = barHeight;
		invalidateSize();
		// }
	}

	/**
	 * Sets the desired bar width for the barcode. This is the width (in pixels)
	 * of the thinnest bar in the barcode. Other bars will change their size
	 * relative to this. <p/> Note that changing this setting after a barcode
	 * has been drawn will invalidate the component and may force a refresh.
	 * 
	 * @param barWidth
	 *            The desired width of the thinnest bar in pixels
	 */
	public void setBarWidth(int barWidth) {
		if (barWidth >= 1) {
			this.width = barWidth;
		} else {
			this.width = 1;
		}
		invalidateSize();
	}

	// /**
	// * Returns the text that will be displayed underneath the barcode (if
	// * requested).
	// *
	// * @return The text label for the barcode
	// */
	// public String getLabel() {
	// if (label != null) {
	// return label;
	// } else {
	// return beautify(data);
	// }
	// }

	/**
	 * Indicates whether the leading and trailing white space should be
	 * rendered. <p/> Note that changing this setting after a barcode has been
	 * drawn will invalidate the component and may force a refresh.
	 * 
	 * @param drawingQuietSection
	 *            True if the quiet area/white space should be shown, false if
	 *            not
	 */
	public void setDrawingQuietSection(boolean drawingQuietSection) {
		this.drawingQuietSection = drawingQuietSection;
		invalidateSize();
	}

	/**
	 * Indicates whether the barcode data should be shown as a string underneath
	 * the barcode or not. <p/> Note that changing this setting after a barcode
	 * has been drawn will invalidate the component and may force a refresh.
	 * 
	 * @param drawingText
	 *            True if the text should be shown, false if not
	 */
	public void setDrawingText(boolean drawingText) {
		this.drawingText = drawingText;
		invalidateSize();
	}

	// protected String beautify(String s) {
	// StringBuffer buf = new StringBuffer();
	// StringCharacterIterator iter = new StringCharacterIterator(s);
	// for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
	// {
	// if (Character.isDefined(c) && !Character.isISOControl(c)) {
	// buf.append(c);
	// }
	// }
	// return buf.toString();
	// }

	/**
	 * Sets the font to use when drawing the barcode data string underneath the
	 * barcode. <p/> Note that changing this setting after a barcode has been
	 * drawn will invalidate the component and may force a refresh.
	 * 
	 * @param font
	 *            The font to use
	 */
	public void setFont(Font font) {
		this.font = font;
		invalidateSize();
	}

	/**
	 * Sets the human readable text to be displayed underneath the barcode. If
	 * set to null then the text will automaticaly be generated.
	 * 
	 * @param label
	 *            the human readable barcode text
	 * @see #getLabel()
	 */
	public void setLabel(String label) {
		this.label = label;
	}

	public String toString() {
		return this.getData();
	}

	protected void updateModel() {}

}
